/*
 * The Schedule Checker for the 
 * 
 * Author: Gavin Greene
 * 
 * Version  May 21, 2011:
 * Initial commit, basic functionality with Student Course preferences.
 * 
 * Version  May 23, 2011
 * Added Teacher course constraints checking.
 * 
 * Version  May 24, 2011
 * Added Advisor Course Constraints Checking.
 * 
 * Version  May 25, 2011
 * Added
 * 
 * Version May 27
 * Credit to Jonnathen Caddey - Refactored Class to return data only upon methods being 
 * called as opposed to returning a single Array of Constraint object lists
 * 
 * 
 * 
 * Version May 31
 * Implemented Business rules.
 * 
 */
package scheduleGenerator;

import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import user.User;
import user.UserType;
import Constraint.ClassConstraintMet;
import Constraint.ClassConstraintViolation;
import Constraint.ConstraintInfo;
import Constraint.TeacherNotAssigned;
import Constraint.TimeConstraintMet;
import Constraint.TimeConstraintViolation;
import Constraint.Violations;
import businessRule.BizRule;
import businessRule.BusinessRuleViolation;
import businessRule.TeacherMaxCreditLoad;
import businessRule.TeacherTwoClassesAtOnce;
import data.ClassPreference;
import data.ClassSlot;
import data.Database;
import data.MapUtil;
import data.TimeSlot;

/**
 * @author Gavin Greene
 * @version May 15, 2011
 * 
 * This class checks a schedule upon construction and 
 * allows for calling classes to obtain information via 
 * the getters for the constraint info objects.
 * 
 * @see ConstraintInfo
 * @see BusinessRuleViolation
 */
public class ScheduleChecker {

	private List<BizRule> myBusinessRules;
	
	private List<ConstraintInfo> constraintsMet;
	private List<ConstraintInfo> constraintsViolated;
	private List<BusinessRuleViolation> businessRulesViolated;

	/**
	 * a private global field to use as a label for the agregrate student users when 
	 */
	private final User myAggregateStudent;


	/**
	 * A database reference.
	 */
	private Database myDb;
	/**
	 * The schedule of classes.
	 */
	private List<ClassSlot> mySecheduleClasses;

	/**
	 * The schedule of times.
	 */
	private List<TimeSlot> mySecheduleTimes;
	/**
	 * A utility class for sorting the map for student times / Class preferences.
	 */
	MapUtil map_Sorter;
	/**
	 * Checks the Schedules. Will add more soon
	 * 
	 * @param the_db
	 * @param the_sechedule
	 */
	public ScheduleChecker(final List<ClassSlot> theSecheduleClasses, final List<TimeSlot> theScheduleTimes, final Database theDb){

		myDb = theDb;
		mySecheduleClasses = theSecheduleClasses;
		mySecheduleTimes = theScheduleTimes;
		map_Sorter = new MapUtil();

		myBusinessRules = new LinkedList<BizRule>();
		CreateBusinessRules(myBusinessRules);
		constraintsMet = new ArrayList<ConstraintInfo>();
		constraintsViolated = new ArrayList<ConstraintInfo>();
		businessRulesViolated = new ArrayList<BusinessRuleViolation>();

		myAggregateStudent = new User(0, new String ("At least " + 
				Database.STUDENT_CUT_OFF_PERCENTAGE * 100 + " % of Students"),
				new UserType[]{UserType.STUDENT});
		
		
		/* Check the Constraints */
		checkSchedule();
	}

	/**
	 * 
	 * Called Automatically by Constructor:
	 * Checks a schedule against the known constraints 
	 * and business rules in the database.
	 * Returns the list of met and violated constraints and 
	 * business rules for the current schedule .
	 * 
	 */
	private void checkSchedule(){

		//Get constraint Data:
		List<User> AdvisorPreferences = myDb.getAvisors();

		List<ClassPreference> TopStudentClasses = myDb.getStudentClassConstraints();
		List<TimeSlot> TopStudentTimes = myDb.getStudentTimeConstraints();

		Map<String, User> InstructerPrefs = myDb.getTeacherConstraints();

		//Process and Check Schedule:
		checkAdvisorConstraints(AdvisorPreferences);
		checkStudentClassConstraints(TopStudentClasses);
		checkStudentTimeConstraints(TopStudentTimes);
		checkTeacherConstraints(InstructerPrefs);
		checkBusinessRules(myBusinessRules);
	}

	public List<ConstraintInfo> getConstraintsMet(){
		return constraintsMet;
	}
	
	public List<ConstraintInfo> getConstraintsViolated(){
		return constraintsViolated;
	}
	
	public List<BusinessRuleViolation> getBusinessRulesViolated(){
		return businessRulesViolated;
	}
	
	
	/**
	 * @pre the advisor list is not null
	 * @pre a global schedule has been created, and stored in my_Sechedule varlabie.
	 * @pre a global constraints met & 
	 * constraints violated List has been created, and 
	 * stored in constraintsMet &  constraintsViolated
	 * 
	 * @post Writes the constraints met and violated to the appropriate Lists in string format.
	 * 
	 *  Checks the class constraints for the Students.
	 *  Checks the inputed List of class slots against the proposed schedule
	 *  Writes the Constraints met / violated to the global list of ConstraintInfo Objects. 
	 *  
	 * @param theConstraints the list of constraints
	 * 
	 */
	private void checkStudentClassConstraints(final List<ClassPreference> theConstraints){
		
		for (ClassPreference course : theConstraints){
			if(mySecheduleClasses.contains(course)){
				constraintsMet.add(new ClassConstraintMet(myAggregateStudent, course, UserType.STUDENT));
			}
			else{
				constraintsViolated.add(new ClassConstraintViolation(myAggregateStudent,
						course, UserType.STUDENT, Violations.CLASSVIOLATION));
			}
		}
	}

	/**
	 *  Checks the Time and Day Constraints for the Students. 
	 * 
	 *  
	 * @param theConstraints the list of time constraints
	 * 
	 */
	private void checkStudentTimeConstraints(final List<TimeSlot> theConstraints){	
		
		/* for each time slot that was requested */
		for (TimeSlot requestedTime : theConstraints){
			/* check to see if the given schedule contains a class in that time block */
			if(mySecheduleTimes.contains(theConstraints)){
				constraintsMet.add(new TimeConstraintMet(myAggregateStudent,
						requestedTime, UserType.STUDENT));
			}
			else{
				constraintsMet.add(new TimeConstraintViolation(myAggregateStudent,
						requestedTime, UserType.STUDENT, Violations.TIMEVIOLATION));
			}
		}
	}
	
	/**
	 * @pre the advisor list is not null
	 * @pre a global sechedule has been created, and stored in Myschedule
	 * @pre a global constraints met & 
	 * constraints violated List has been created, and 
	 * stored in constraintsMet &  constraintsViolated
	 * 
	 * @post Writes the constraints met and violated to the appropriate Lists in the form of ConstraintInfo Objects.
	 * 
	 * checks the current schedule against the Adviser's preferences.
	 * 
	 * @param advisorClassRecommendations a List of Adviser Users
	 * 
	 */
	void checkAdvisorConstraints(List<User> advisorClassRecommendations){
		/* for each user on the advisor list, search to see if their classes are offered */
		for(User advisor : advisorClassRecommendations){
			for(ClassPreference course : advisor.getClasses(UserType.ADVISER)){
				if(mySecheduleClasses.contains(course)){
					constraintsMet.add(new ClassConstraintMet(advisor, course, UserType.ADVISER));
				}
				else{
					constraintsViolated.add(new ClassConstraintViolation(advisor, course, UserType.ADVISER, Violations.CLASSVIOLATION) );
				}
			}
			/* end of advisor's class recommendations loop */
		}
		/* end of advisor users loop */
	}


	/**
	 * @pre a global sechedule has been created, and stored in Myschedule
	 * @pre a global constraints met & 
	 * constraints violated List has been created, and 
	 * stored in constraintsMet &  constraintsViolated
	 * 
	 * @post Writes the constraints met and violated to the appropriate Lists in ConstraintInfo Format.
	 *  
	 * checks each teacher's assigned course in the 
	 * current schedule against the teacher's preferences.
	 * 
	 * Also checks each teacher's requested time blocks against the schedule's time blocks.
	 * 
	 * @param instructerPrefs a Map of instructor constraints, such that:
	 * InstructorName -> Instructor User.
	 * 
	 */
	void checkTeacherConstraints(
			Map<String, User> instructerPrefs) {
		for(ClassSlot course : mySecheduleClasses){
			User currentTeacher = 
				instructerPrefs.get(course.getClassTeacher());
			
			if(currentTeacher != null){
			List<TimeSlot> insturctorTimePrefs = 
				currentTeacher.getTimes(UserType.TEACHER);

			TimeSlot courseTime = course.getTimeSlot();

			/* If the teacher for the course is in the map teachers who provided feedback. */
		  
				if(currentTeacher.getClasses(UserType.TEACHER).contains(course)){
					constraintsMet.add(new ClassConstraintMet(currentTeacher, course, UserType.TEACHER));
				}
				else{
					constraintsViolated.add(new ClassConstraintViolation(currentTeacher, course,
							UserType.TEACHER, Violations.CLASSVIOLATION));
				}
			/* If the teacher's Time blocks are reflected in their classes */	
				
				//System.out.println("Checking Instructor for: " + courseTime);
				if(insturctorTimePrefs.contains(courseTime)){
					constraintsMet.add(new TimeConstraintMet(currentTeacher, courseTime, UserType.TEACHER));
				}
				else{
					constraintsViolated.add(new TimeConstraintViolation(currentTeacher, courseTime, 
							UserType.TEACHER, Violations.TIMEVIOLATION));
				}
			}
			
			/* we fall into this case when we don't have feedback from the current teacher  */ 
			else
			{
				
				/* we didn't have feedback from 
				 * this teacher, currently do nothing */
			}
			
		}
		/* End of Schedule Class Loop */
		detectMissingTeachers(instructerPrefs.values());
		
		}
	
	/**
	 * detects teachers that provided feedback, but were not assigned any classes in the schedule.
	 * @return a list of the missing teachers in the form List<User>
	 * 
	 */
	private void detectMissingTeachers(Collection<User> theFeedbackTeachers){
		/* for each instructor who has provided feedback */
		ArrayList<User> MissingTeachers = new ArrayList<User>();
		ArrayList<String> ScheduleTeachers = new ArrayList<String>();
		
		for(ClassSlot course : mySecheduleClasses){
			//create list of all schedule teachers
			ScheduleTeachers.add(course.getClassTeacher());
		}
		/* end of class slot for loop */
		
		for (User teacher : theFeedbackTeachers){
			/* check to see if that instructor is in the schedule */
			if(ScheduleTeachers.contains(teacher.getName())){
				// the teacher who provided feedback is on the schedule - do nothing
			}
			else{
				//the teacher provided feedback but was not on the schedule. 
				if(MissingTeachers.contains(teacher)){
					// the teacher has already been detected
				}
				else
				{
					MissingTeachers.add(teacher);
					constraintsViolated.add(new TeacherNotAssigned(teacher,
							UserType.TEACHER, Violations.TEACHERNOTASSIGNED));
					
				}
				
			}
			
		}
		/* end of teacher for loop */

	}
	/**
	 * This adds business rules. Future releases will make this public, so anyone can add business rules, and change the structure to return 
	 * @param theRules
	 */
	private void CreateBusinessRules(List<BizRule> theRules){
		theRules.add(new TeacherTwoClassesAtOnce());
		theRules.add(new TeacherMaxCreditLoad(myDb.getTeachers()));
	}
	
	private void checkBusinessRules(List<BizRule> myBusinessRules){
		for(BizRule rule : myBusinessRules){
			rule.CheckRule(mySecheduleClasses, businessRulesViolated);
		}
	}

	

}
